package com.kk.xhr.admin.biz.impl;

import com.kk.xhr.admin.biz.ISignBizService;
import com.kk.xhr.admin.controller.request.ApplyQueryRequest;
import com.kk.xhr.admin.controller.request.ApplyUpdateRequest;
import com.kk.xhr.admin.controller.request.ComplementSignAddRequest;
import com.kk.xhr.admin.controller.request.SignQueryRequest;
import com.kk.xhr.admin.controller.vo.ComplementSignVO;
import com.kk.xhr.admin.controller.vo.SignVo;
import com.kk.xhr.admin.util.FileUtil;
import com.kk.xhr.common.Constants;
import com.kk.xhr.common.ErrorCodeEnum;
import com.kk.xhr.common.exception.BusinessException;
import com.kk.xhr.common.pojo.ApplyStatusEnum;
import com.kk.xhr.common.pojo.BinaryEnum;
import com.kk.xhr.common.pojo.SignStatusEnum;
import com.kk.xhr.model.dto.ApplyQueryRequestDTO;
import com.kk.xhr.model.dto.ComplementSignDTO;
import com.kk.xhr.model.dto.LeaveDTO;
import com.kk.xhr.model.dto.NotificationDTO;
import com.kk.xhr.model.dto.PageDto;
import com.kk.xhr.model.dto.SignDTO;
import com.kk.xhr.model.dto.SignQueryRequestDTO;
import com.kk.xhr.model.dto.UserDTO;
import com.kk.xhr.model.dto.WorkOvertimeDTO;
import com.kk.xhr.model.dto.WorkTimeDTO;
import com.kk.xhr.model.service.ILeaveService;
import com.kk.xhr.model.service.INotificationService;
import com.kk.xhr.model.service.ISignService;
import com.kk.xhr.model.service.IUserService;
import com.kk.xhr.model.service.IWorkOvertimeService;
import com.kk.xhr.model.service.IWorkTimeService;
import com.kk.xhr.util.DateUtil;
import com.kk.xhr.web.vo.PageVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Optional;

/**
 * 签到biz接口实现
 *
 * @author luokexiong
 * @version 1.0 2021/2/19
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class SignBizServiceImpl implements ISignBizService {
    private final ISignService signService;
    private final IUserService userService;
    private final IWorkTimeService workTimeService;
    private final ILeaveService leaveService;
    private final IWorkOvertimeService overtimeService;
    private final INotificationService notificationService;

    /**
     * {@inheritDoc}
     */
    @Override
    public PageVO<SignVo> queryPage(Long userId, SignQueryRequest request) {
        final UserDTO userDTO = userService.queryById(userId).orElseThrow(() ->
            new BusinessException(ErrorCodeEnum.USER_NOT_EXIST));
        PageDto<SignDTO> dtos = signService.query(SignQueryRequestDTO.builder()
            .userId(userId)
            .begin(DateUtil.validTime(request.getBegin()))
            .end(DateUtil.validTime(request.getEnd()))
            .status(request.getStatus())
            .offset(request.getOffset())
            .limit(request.getLimit())
            .build());
        if (0 == dtos.getTotal()) {
            return PageVO.empty();
        }
        final String name = userDTO.getName();
        return PageVO.fromDTO(dtos, dto -> SignVo.builder()
            .id(dto.getId())
            .name(name)
            .duration(dto.getDuration())
            .begin(dto.getBegin())
            .beginStatus(dto.getBeginStatus())
            .end(dto.getEnd())
            .endStatus(dto.getEndStatus())
            .signDate(dto.getSignDate())
            .lack(dto.getLack())
            .status(dto.getStatus())
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PageVO<ComplementSignVO> queryPage(Long userId, ApplyQueryRequest request) {
        PageDto<ComplementSignDTO> dtos = signService.query(userId,
            ApplyQueryRequestDTO.builder()
                .userId(userId)
                .begin(DateUtil.validTime(request.getBegin()))
                .end(DateUtil.validTime(request.getEnd()))
                .status(request.getStatus())
                .offset(request.getOffset())
                .limit(request.getLimit())
                .build());
        if (0 == dtos.getTotal()) {
            return PageVO.empty();
        }
        UserDTO userDto = userService.queryNormal(userId).orElseThrow(() -> new BusinessException(ErrorCodeEnum.USER_NOT_EXIST));
        String name = userDto.getName();
        String reviewer = userService.queryName(userDto.getBossId());
        return PageVO.fromDTO(dtos, dto -> ComplementSignVO.builder()
            .id(dto.getId())
            .name(name)
            .reviewer(reviewer)
            .begin(dto.getBegin())
            .end(dto.getEnd())
            .duration(dto.getDuration())
            .type(dto.getType())
            .fileUrl(FileUtil.getFileUrl(dto.getFileId()))
            .status(dto.getStatus())
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PageVO<ComplementSignVO> queryPageCheck(Long userId, ApplyQueryRequest request) {
        PageDto<ComplementSignDTO> dtos = signService.query(null,
            ApplyQueryRequestDTO.builder()
                .reviewerId(userId)
                .begin(DateUtil.validTime(request.getBegin()))
                .end(DateUtil.validTime(request.getEnd()))
                .status(request.getStatus())
                .offset(request.getOffset())
                .limit(request.getLimit())
                .build());
        if (0 == dtos.getTotal()) {
            return PageVO.empty();
        }
        UserDTO userDto = userService.queryNormal(userId).orElseThrow(() -> new BusinessException(ErrorCodeEnum.USER_NOT_EXIST));
        String name = userDto.getName();
        String reviewer = userService.queryName(userDto.getBossId());
        return PageVO.fromDTO(dtos, dto -> ComplementSignVO.builder()
            .id(dto.getId())
            .name(name)
            .reviewer(reviewer)
            .begin(dto.getBegin())
            .end(dto.getEnd())
            .duration(dto.getDuration())
            .type(dto.getType())
            .fileUrl(FileUtil.getFileUrl(dto.getFileId()))
            .status(dto.getStatus())
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void sign(Long userId, LocalDateTime now) {
        LocalTime localTime = now.toLocalTime();
        // 查询当日最新签到记录
        Optional<SignDTO> optional = signService.query(userId, now.toLocalDate());
        // 获取最新签到时间设置
        WorkTimeDTO workTime = workTimeService.queryLatest().orElse(
            WorkTimeDTO.builder()
                .earlyBegin(Constants.WORK_EARLY_BEGIN)
                .earlyEnd(Constants.WORK_EARLY_END)
                .lateBegin(Constants.WORK_LATE_BEGIN)
                .lateEnd(Constants.WORK_LATE_BEGIN)
                .flexDuration(30)
                .duration(8)
                .status(BinaryEnum.TRUE)
                .build()
        );
        // 记录不存在或无早签记录
        if (!optional.isPresent() || null == optional.get().getBegin()) {
            // 校验时间，若当天无签到，则add,若超时，状态为LATE,未超时
            if (localTime.isBefore(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()))) {
                signService.add(SignDTO.builder()
                    .userId(userId)
                    .begin(now)
                    .beginStatus(SignStatusEnum.NORMAL)
                    .endStatus(SignStatusEnum.ABNORMAL)
                    .signDate(LocalDateTime.of(now.toLocalDate(), LocalTime.MIN))
                    .build());
            } else if (localTime.isAfter(workTime.getLateEnd())) {
                // 下班时间早签，直接异常
                throw new BusinessException(ErrorCodeEnum.SIGN_ERROR);
            } else {
                signService.add(SignDTO.builder()
                    .userId(userId)
                    .begin(now)
                    .beginStatus(SignStatusEnum.LATE)
                    .endStatus(SignStatusEnum.ABNORMAL)
                    .signDate(LocalDateTime.of(now.toLocalDate(), LocalTime.MIN))
                    .build());
            }
        } else {
            // 存在签到记录
            SignDTO signDto = optional.get();
            LocalTime trulyBeginTime = signDto.getBegin().toLocalTime();
            // 计算实际下班时间
            LocalTime trulyEndTime = getTrulyEndTime(trulyBeginTime, workTime);
            // 计算lack和duration
            int lack = computeLackTime(trulyBeginTime, trulyEndTime, localTime, workTime, false);
            final float dur = (float) (workTime.getDuration() * 60 - lack) / 60;
            if (0 == lack) {
                signService.add(signDto.setEnd(now).setEndStatus(SignStatusEnum.NORMAL).setDuration(dur).setLack(lack));
            } else {
                signService.add(signDto.setEnd(now).setEndStatus(SignStatusEnum.ABNORMAL).setDuration(dur).setLack(lack));
            }
            signService.modify(SignDTO.builder().id(signDto.getId()).status(BinaryEnum.FALSE).build());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void complementSign(Long userId, ComplementSignAddRequest request) {
        if (null != request.getBegin() && null != request.getEnd()
            && !checkDate(request.getBegin(), request.getEnd())) {
            throw new BusinessException(ErrorCodeEnum.DATE_ERROR);
        }
        if (null == request.getEnd()) {
            throw new BusinessException(ErrorCodeEnum.DATE_ERROR);
        }
        UserDTO userDto = userService.queryNormal(userId).orElseThrow(() -> new BusinessException(ErrorCodeEnum.USER_NOT_EXIST));
        signService.addComplement(ComplementSignDTO.builder()
            .userId(userId)
            .reviewerId(userDto.getBossId())
            .begin(request.getBegin())
            .end(request.getEnd())
            .duration(request.getDuration())
            .fileId(request.getFileId())
            .type(request.getType())
            .status(ApplyStatusEnum.APPLYING)
            .build());
        notificationService.add(NotificationDTO.builder()
            .userId(userDto.getBossId())
            .message("您有待审核的签到申请，请查看")
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkApply(Long userId, ApplyUpdateRequest request) {
        if (null == request.getType()) {
            throw new BusinessException(ErrorCodeEnum.BAD_REQUEST);
        }
        switch (request.getType()) {
            case LEAVE:
                this.checkLeave(userId, request);
                break;
            case WORK_OVERTIME:
                this.checkWorkOvertime(userId, request);
                break;
            case COMPLEMENT_SIGN:
                this.checkSign(userId, request);
                break;
            default:
                throw new BusinessException(ErrorCodeEnum.APPLY_RECODE_NOT_EXIST);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateReportOfDay(Long userId) {
        LocalDate now = LocalDate.now();
        WorkTimeDTO workTime = workTimeService.queryLatest().orElse(
            WorkTimeDTO.builder()
                .earlyBegin(Constants.WORK_EARLY_BEGIN)
                .earlyEnd(Constants.WORK_EARLY_END)
                .lateBegin(Constants.WORK_LATE_BEGIN)
                .lateEnd(Constants.WORK_LATE_BEGIN)
                .flexDuration(30)
                .duration(8)
                .status(BinaryEnum.TRUE)
                .build());
        Optional<SignDTO> optional = signService.query(userId, now);
        if (optional.isPresent()) {
            SignDTO sign = optional.get();
            String message = null;
            if (null == sign.getEnd()) {
                message = String.format("今日考勤情况：时长 0小时, 缺失时长: %d分钟, 早签时间: %tR, 晚签缺勤",
                    workTime.getDuration() * 60, sign.getBegin());
            } else {
                message = String.format("今日考勤情况：时长 %f小时, 缺失时长: %d分钟, 早签时间: %tR, 晚签时间: %tR",
                    sign.getDuration(), sign.getLack(), sign.getBegin(), sign.getEnd());
            }
            notificationService.add(NotificationDTO.builder()
                .userId(userId)
                .message(message)
                .build());
        } else {
            signService.add(SignDTO.builder()
                .userId(userId)
                .duration(workTime.getDuration().floatValue())
                .lack(workTime.getDuration() * 60)
                .signDate(LocalDateTime.of(now, LocalTime.MIN))
                .build());
            notificationService.add(NotificationDTO.builder()
                .userId(userId)
                .message("考勤出现异常，请前往考勤管理查看")
                .build());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void generateReportOfMouth(Long userId) {
        // TODO:(LuoKeXiong 2021/2/21): 生成每个人的报告
    }

    /**
     * 判断时间是否为同一天
     *
     * @param begin 开始时间
     * @param end   结束时间
     * @return 是否相同
     */
    private boolean checkDate(LocalDateTime begin, LocalDateTime end) {
        // 年、月、日相同
        return begin.toLocalDate().isEqual(end.toLocalDate());
    }

    /**
     * 计算实际下班时间
     *
     * @param trulyBeginTime 实际上班时间
     * @param workTime       工作时间
     * @return 实际下班时间
     */
    private LocalTime getTrulyEndTime(LocalTime trulyBeginTime, WorkTimeDTO workTime) {
        LocalTime trulyEndTime;
        if (trulyBeginTime.isBefore(workTime.getEarlyBegin())) {
            // 早于8:30
            trulyEndTime = workTime.getLateEnd();
        } else if (trulyBeginTime.isBefore(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()))) {
            // 8:30 - 9:00
            // 计算实际下班时间
            trulyEndTime = workTime.getLateEnd()
                .plusMinutes((int) Math.abs(Duration.between(trulyBeginTime, workTime.getEarlyBegin()).toMinutes()));
        } else {
            // 超过弹性上班时间
            trulyEndTime = workTime.getLateEnd().plusMinutes(workTime.getFlexDuration());
        }
        return trulyEndTime;
    }

    /**
     * 计算缺少时间
     *
     * @param now            打卡时间
     * @param workTime       工作时间设置
     * @param trulyBeginTime 实际早签时间
     * @param trulyEndTime   实际晚签时间
     * @param flag           是否为早签
     * @return 缺失时长
     */
    private int computeLackTime(LocalTime trulyBeginTime, LocalTime trulyEndTime, LocalTime now, WorkTimeDTO workTime, boolean flag) {
        int lack = 0;
        // 计算早签缺失时间
        if (trulyBeginTime.isBefore(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()))){
            // lack为0
        }else if (trulyBeginTime.isAfter(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration())) &&
            trulyBeginTime.isBefore(workTime.getEarlyEnd())) {
            // 9:00 - 12:00
            lack += Math.abs(Duration.between(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()), trulyBeginTime)
                .toMinutes());
        } else if (trulyBeginTime.isAfter(workTime.getEarlyEnd()) && trulyBeginTime.isBefore(workTime.getLateBegin())) {
            // 12:00 - 13:30
            lack += Math.abs(Duration.between(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()), workTime.getEarlyEnd())
                .toMinutes());
        } else if (trulyBeginTime.isAfter(workTime.getLateBegin()) && trulyBeginTime.isBefore(trulyEndTime)) {
            // 13:30 - trulyEndTime
            lack += Math.abs(Duration.between(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()), workTime.getEarlyEnd()).toMinutes()) +
                Math.abs(Duration.between(workTime.getLateBegin(), now).toMinutes());
        } else {
            throw new BusinessException(ErrorCodeEnum.SIGN_ERROR);
        }
        // 晚签
        if (!flag) {
            // 计算晚签缺失时间
            if (now.isBefore(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration()))) {
                // 9:00之前
                lack += workTime.getDuration() * 60 - Duration.between(trulyBeginTime, now).toMinutes();
            } else if (now.isAfter(workTime.getEarlyBegin().plusMinutes(workTime.getFlexDuration())) &&
                now.isBefore(workTime.getEarlyEnd())) {
                // 9:00 - 12:00
                lack += Math.abs(Duration.between(now, workTime.getEarlyEnd()).toMinutes()) +
                    Duration.between(workTime.getLateBegin(), trulyEndTime).toMinutes();
            } else if (now.isAfter(workTime.getEarlyEnd()) && now.isBefore(workTime.getLateBegin())) {
                // 12:00 - 13:30
                lack += Duration.between(workTime.getLateBegin(), trulyEndTime).toMinutes();
            } else if (trulyBeginTime.isAfter(workTime.getLateBegin()) && trulyBeginTime.isBefore(trulyEndTime)) {
                // 13:30 - trulyEndTime
                lack += Duration.between(now, trulyEndTime).toMinutes();
            }
        }
        return lack;
    }

    /**
     * 处理加班申请
     *
     * @param userId  审核人id
     * @param request 修改申请
     */
    private void checkWorkOvertime(Long userId, ApplyUpdateRequest request) {
        Optional<WorkOvertimeDTO> optional = overtimeService.query(request.getBizId());
        if (!optional.isPresent()) {
            throw new BusinessException(ErrorCodeEnum.APPLY_RECODE_NOT_EXIST);
        }
        if (!optional.get().getReviewerId().equals(userId)) {
            throw new BusinessException(ErrorCodeEnum.REVIEWER_NOT_EQUAL);
        }
        if (ApplyStatusEnum.FAIL == request.getStatus()) {
            leaveService.modify(request.getBizId(), ApplyStatusEnum.FAIL);
        } else if (ApplyStatusEnum.REVOKE == request.getStatus()) {
            leaveService.modify(request.getBizId(), ApplyStatusEnum.REVOKE);
        } else if (ApplyStatusEnum.PASS == request.getStatus()) {
            overtimeService.modify(request.getBizId(), ApplyStatusEnum.PASS);
        }
        notificationService.add(NotificationDTO.builder()
            .userId(userId)
            .message("您的加班审核已完成，请查看")
            .build());
    }

    /**
     * 处理请假申请
     *
     * @param userId  审核人id
     * @param request 修改申请
     */
    private void checkLeave(Long userId, ApplyUpdateRequest request) {
        LeaveDTO leave = leaveService.query(request.getBizId())
            .orElseThrow(() -> new BusinessException(ErrorCodeEnum.APPLY_RECODE_NOT_EXIST));
        if (!leave.getReviewerId().equals(userId)) {
            throw new BusinessException(ErrorCodeEnum.REVIEWER_NOT_EQUAL);
        }
        if (ApplyStatusEnum.FAIL == request.getStatus()) {
            leaveService.modify(request.getBizId(), ApplyStatusEnum.FAIL);
        } else if (ApplyStatusEnum.REVOKE == request.getStatus()) {
            leaveService.modify(request.getBizId(), ApplyStatusEnum.REVOKE);
        } else if (ApplyStatusEnum.PASS == request.getStatus()) {
            // TODO:(kkmystery 2021/3/3) :补充请假流程
            // 当为补假时需要对以前的考勤记录进行修改
            WorkTimeDTO workTime = workTimeService.queryLatest()
                .orElse(WorkTimeDTO.builder()
                    .earlyBegin(Constants.WORK_EARLY_BEGIN)
                    .earlyEnd(Constants.WORK_EARLY_END)
                    .lateBegin(Constants.WORK_LATE_BEGIN)
                    .lateEnd(Constants.WORK_LATE_BEGIN)
                    .flexDuration(30)
                    .duration(8)
                    .status(BinaryEnum.TRUE)
                    .build());
            if (leave.getEnd().toLocalDate().isBefore(LocalDate.now())) {
                // 补签
                int days = (int) Duration
                    .between(leave.getBegin().toLocalDate(), leave.getEnd().toLocalDate()).toDays() + 1;
                for (int i = 0; i < days; i++) {
                    LocalDate day = leave.getBegin().toLocalDate().plusDays(i);
                    supplySign(userId, leave, workTime, day);
                }
            }
            leaveService.modify(request.getBizId(), ApplyStatusEnum.PASS);
        }
        notificationService.add(NotificationDTO.builder()
            .userId(userId)
            .message("您的请假审核已完成，请查看")
            .build());
    }

    /**
     * 根据补假信息修改以前的考勤记录
     *
     * @param userId   用户id
     * @param leave    用户请假记录
     * @param workTime 工作时间
     * @param signDate 补签当天日期
     */
    private void supplySign(Long userId, LeaveDTO leave, WorkTimeDTO workTime, LocalDate signDate) {
        // 查询当天考勤记录
        Optional<SignDTO> optional = signService.query(userId, signDate);
        if (optional.isPresent()) {
            SignDTO sign = optional.get();
            LocalTime trulyEndTime = this.getTrulyEndTime(sign.getBegin().toLocalTime(), workTime);
            if (LocalDateTime.of(signDate, trulyEndTime).isBefore(leave.getEnd())) {
                if (leave.getBegin().isBefore(sign.getEnd())) {

                    int lack = this.computeLackTime(sign.getBegin().toLocalTime(), trulyEndTime, workTime.getLateEnd(), workTime, false);
                    sign.setLack(Math.min(sign.getLack() - lack, 0));
                    sign.setDuration((float) (workTime.getDuration() * 60 - lack) / 60);
                    if (0 == lack) {
                        sign.setEndStatus(SignStatusEnum.NORMAL);
                    } else {
                        sign.setEndStatus(SignStatusEnum.ABNORMAL);
                    }
                    signService.modify(sign);
                }
            }
        } else {
            // 若无考勤记录则生成
            this.sign(userId, leave.getBegin());
            this.sign(userId, leave.getEnd());
        }

    }

    /**
     * 处理补签
     *
     * @param userId  审核人id
     * @param request 修改申请
     */
    private void checkSign(Long userId, ApplyUpdateRequest request) {
        if (ApplyStatusEnum.FAIL == request.getStatus()) {
            signService.modifyComplementStatus(request.getBizId(), ApplyStatusEnum.FAIL);
        } else if (ApplyStatusEnum.REVOKE == request.getStatus()) {
            signService.modifyComplementStatus(request.getBizId(), ApplyStatusEnum.REVOKE);
        } else if (ApplyStatusEnum.PASS == request.getStatus()) {
            // 查询申请记录
            Optional<ComplementSignDTO> complementSignDto = signService.queryComplementById(request.getBizId());
            if (!complementSignDto.isPresent()) {
                throw new BusinessException(ErrorCodeEnum.COMPLEMENT_SIGN_NOT_EXIST);
            }
            ComplementSignDTO complementSign = complementSignDto.get();
            if (!complementSignDto.get().getReviewerId().equals(userId)) {
                throw new BusinessException(ErrorCodeEnum.REVIEWER_ERROR);
            }
            // 查询当日签到信息
            Optional<SignDTO> signDto = signService.query(complementSign.getUserId(), complementSign.getEnd().toLocalDate());
            // 当日无签到记录
            if (!signDto.isPresent()) {
                // 若不存在则补早签
                this.sign(complementSign.getUserId(), complementSign.getBegin());
            } else {
                SignDTO sign = signDto.get();
                if (null != complementSign.getBegin() && sign.getBegin().isAfter(complementSign.getBegin())) {
                    signService.modify(SignDTO.builder().id(sign.getId()).status(BinaryEnum.FALSE).build());
                    this.sign(complementSign.getUserId(), complementSign.getBegin());
                }
            }
            // 若存在分两种情况，1、未晚签，2、有晚签
            this.sign(complementSign.getUserId(), complementSign.getEnd());
            signService.modifyComplementStatus(request.getBizId(), ApplyStatusEnum.PASS);
        }
        notificationService.add(NotificationDTO.builder()
            .userId(userId)
            .message("您的补签审核已完成，请查看")
            .build());
    }
}
